﻿using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Services;
using Utilities;
using WAS.Logic;
using WAS.Logic.SampleLevel;

namespace WAS
{

  /// <summary>
  /// EffectsManager gestisce i sistemi particellari necessari nel gioco e si occupa di passare loro
  /// le informazioni aggiornate sulla camera (che ottiene in quanto CameraConsumer) e di aggiornare 
  /// le animazioni per le texture con tanti fotogrammi tipo le esplosioni).
  /// </summary>
  public class EffectsManager : CameraConsumer
  {

    public class LightningEffect
    {
        //private ArrayList<ThunderBoltSegmentation> segments;
        private bool projectileActive;
        public Vector3 endPoint;
        public Vector3 startPoint;
        public Projectile projectile;
        public float duration;
        public float slow_motion;

        public float scale;

        public float size;
        private JigLibX.Physics.Body shooted_body;
        private Ship shooter;

        public LightningEffect() { }

        public LightningEffect(Projectile projectile, float duration)
        {
            Init(projectile, duration);
        }

        public void Init(Projectile projectile, float duration)
        {
            this.projectileActive = true;
            this.projectile = projectile;
            this.duration = duration;
            this.size = 20.0f;//projectile.weapon_config.Damage * 10;
            
            this.shooter = projectile.shooter;
        }


        public void Update(GameTime gameTime)
        {
            if (!shooter.OwnValue.IsActive)
            {
                duration = 0.0f;
                return;
            }

            startPoint = shooter.geometry.Position;

            if (projectileActive)
            {
                endPoint = projectile.geometry.Position;
            }
            else
            {
                if (shooted_body == null)
                {
                    duration = 0.0f;
                    return;
                }
                if (!
                    ((shooted_body.ExternalData is Ship && ((Ship)shooted_body.ExternalData).OwnValue.IsActive)
                    || (shooted_body.ExternalData is Asteroid && ((Asteroid)shooted_body.ExternalData).OwnValue.IsActive))
                    )
                {
                    duration = 0.0f;
                    return;
                }
                endPoint = shooted_body.Position;
            }

            if (projectileActive && !projectile.OwnValue.IsActive)
            {
                shooted_body = projectile.shot_body;
                projectileActive = false;
                if (shooted_body == null)
                {
                    duration = 0.0f;
                    return;
                }
            }
           
            duration -= (float)gameTime.ElapsedGameTime.TotalSeconds * slow_motion;
        }

        public float rotation
        {
            get
            {
                return -(float)(Math.Atan2((float)(endPoint.X - startPoint.X), (float)(endPoint.Z - startPoint.Z)));
            }
        }
    }

    public class LaserLineEffect
    {
        public bool projectileActive;
        public Vector3 endPoint;
        public Vector3 startPoint;
        public Projectile projectile;
        public float duration;
        public float slow_motion;

        private JigLibX.Physics.Body target_body;
        private Ship shooter;

        private bool atarget;
        private float dist;

        public float size;

        public LaserLineEffect() { }

        public LaserLineEffect(Projectile projectile, float duration)
        {
            Init(projectile, duration);
        }

        public void Init(Projectile projectile, float duration)
        {
            this.projectile = projectile;
            this.duration = duration;
            this.projectileActive = true;
            this.atarget = false;
            this.shooter = projectile.shooter;
            this.startPoint = shooter.geometry.Position;
            this.endPoint = startPoint;
            this.target_body = projectile.target_geometry;
            this.size = 20f;
        }

        public void Update(GameTime gameTime)
        {
            if (!shooter.OwnValue.IsActive ||
                !((target_body.ExternalData is Ship && ((Ship)target_body.ExternalData).OwnValue.IsActive)
                    || (target_body.ExternalData is Asteroid && ((Asteroid)target_body.ExternalData).OwnValue.IsActive))
                )
            {
                duration = 0.0f;
                return;
            }

            startPoint = shooter.geometry.Position;

            if (!projectileActive)
            {
                if (atarget)
                    endPoint = target_body.Position;
                else
                    endPoint = Vector3.Normalize(target_body.Position - shooter.geometry.Position) * dist + shooter.geometry.Position;
                duration -= (float)gameTime.ElapsedGameTime.TotalSeconds * slow_motion;
            }
            else
                dist = Vector3.Distance(projectile.geometry.Position, shooter.geometry.Position);

            if (projectileActive && !projectile.OwnValue.IsActive)
            {
                atarget = projectile.target_geometry == projectile.shot_body;                
                projectileActive = false;
            }
            

        }

        public float rotation
        {
            get
            {
                return -(float)(Math.Atan2((float)(endPoint.X - startPoint.X), (float)(endPoint.Z - startPoint.Z)));
            }
        }
    }

    public class MissileEffect
    {

        public Projectile projectile;
        public bool projectileActive;

        public MissileEffect() { }

        public MissileEffect(Projectile projectile)
        {
            Init(projectile);
        }

        public void Init(Projectile projectile)
        {
            this.projectile = projectile;
            this.projectileActive = true;
        }

        public void Update(GameTime gameTime)
        {
            projectileActive = projectile.OwnValue.IsActive;
        }

        public float rotation
        {
            get
            {
                var p = projectile.geometry.Position;
                var d = projectile.geometry.Velocity+p;
                return -(float)(Math.Atan2((float)(d.X - p.X), (float)(d.Z - p.Z)));
            }
        }
    }

    public class Explosion
    {
      public Vector3 position;
      public float size;
      public float age;
      public void Update(GameTime gameTime) { age += (float)gameTime.ElapsedGameTime.TotalSeconds; }
    }

    WAS.Logic.IStateProvider<BaseLevelStats> state_provider;

    public class EnemyWarp
    {
        public float warping_out_timer = 0.5f;
        
        public WarpParticle particle;
        Ship ship;

        public EnemyWarp(EffectsManager em, Ship ship)
        {            
            this.ship = ship;
            particle = em.AddWarparticle(ship.geometry.Position, 0.0f, Color.White);            
        }

        public void Update(float dt)
        {
            warping_out_timer -= dt;
            var warp_ratio = 1.0f - warping_out_timer / PlayerShip.WARP_TRANSITION_TIME;
            if (warp_ratio > 0.75f)
            {
                warp_ratio = (warp_ratio - 0.75f) * 4.0f;
                particle.Position = ship.geometry.Position +
                    (1.0f - warp_ratio) * ship.geometry.Orientation.Forward * ship.geometry.bounding_sphere().Radius * 0.75f;
                particle.Size = MathHelper.SmoothStep(0.02f, 0.01f, warp_ratio) * 0.9f;
            }
            else
            {
                warp_ratio *= 4.0f / 3.0f;
                particle.Position = ship.geometry.Position +
                    warp_ratio * ship.geometry.Orientation.Forward * ship.geometry.bounding_sphere().Radius * 0.75f;
                particle.Size = MathHelper.SmoothStep(0.05f, 0.02f, warp_ratio) * 0.9f;
            }
        }

        public void Dispose()
        { 
            
        }
    }
    public class PlasmaParticle : Particle { }
    public class EngineParticle : Particle { }
    public class SmokeParticle : Particle { }
    public class WarpParticle : Particle { }
    public class MachineGunParticle : Particle { }
    public class MuzzleFlashParticle : Particle { }

    public class LaserShot
    {
        public Particle LaserLine;
        public Particle ShooterGlobus, TargetGlobus;
        public Vector3 ShooterPosition;
        public Vector3 TargetPosition;
        public Color LaserColor;
        // identificatore da associare allo shot, in modo da poterlo recuperare dalla lista dei lasershot, univocamente
        public float MAXLIFE;
        public float life;
        // rotazione da applicare alla texture, da correggere in caso di texture NON orientata verso destra ( X positiva)
        public float Angle
        {
            get
            {
              return (float)Math.Atan2(
                  (float)(TargetPosition.Z - ShooterPosition.Z), 
                  (float)(TargetPosition.X - ShooterPosition.X));
            }
        }
        // scalatura da applicare alla texture, pari alla distanza tra shooter e target
        public Vector2 Scale
        {
            get
            {
                return new Vector2(0.1f, Vector3.Distance(ShooterPosition, TargetPosition)*0.1f);
            }
        }

        public Vector3 Centroid
        {
            get
            {
                return new Vector3( (ShooterPosition.X + TargetPosition.X) / 2,
                        0.0f, (ShooterPosition.Z + TargetPosition.Z) / 2);
            }
        }
    }

    public class VanishingText
    {
      public Vector3 position;
      public string text;
      public float time_left;
      public void Update(GameTime gameTime) { time_left -= (float)gameTime.ElapsedGameTime.TotalSeconds; }
    }

    ParticleSystem<SmokeParticle> smoke;
    ParticleSystem<PlasmaParticle> plasma;
    ParticleSystem<PlasmaParticle> chainreaction;
    ParticleSystem<MachineGunParticle> machine_gun;
    ParticleSystem<MuzzleFlashParticle> muzzle_flash;
    ParticleSystem<EngineParticle> plasma_engine;
    ParticleSystem<WarpParticle> warp;

    public EffectsManager(Microsoft.Xna.Framework.Game game)
        : base(game) 
    { 
      game.Services.AddService(typeof(EffectsManager), this);
      smoke = new ParticleSystem<SmokeParticle>(Game, @"Explosion\smoke");
      plasma = new ParticleSystem<PlasmaParticle>(Game, @"Explosion\plasma");
      chainreaction = new ParticleSystem<PlasmaParticle>(Game, @"Explosion\chainreaction");
      machine_gun = new ParticleSystem<MachineGunParticle>(Game, @"Explosion\mg_shot");
      muzzle_flash = new ParticleSystem<MuzzleFlashParticle>(Game, @"Explosion\Muzzle_Flash_Side", BlendState.AlphaBlend, 0.025f);
      plasma_engine = new ParticleSystem<EngineParticle>(Game, @"Explosion\plasma");
      warp = new ParticleSystem<WarpParticle>(Game, @"CachedEffects\warp");

      vanishing_text_font = game.Content.Load<SpriteFont>(@"Fonts\Arial14Ptx");
      smoke.DrawOrder = (int)ComponentOrders.EFFECTS;
      plasma.DrawOrder = (int)ComponentOrders.EFFECTS;
      chainreaction.DrawOrder = (int)ComponentOrders.EFFECTS;
      machine_gun.DrawOrder = (int)ComponentOrders.EFFECTS;
      muzzle_flash.DrawOrder = (int)ComponentOrders.EFFECTS;
      plasma_engine.DrawOrder = (int)ComponentOrders.EFFECTS;
      warp.DrawOrder = (int)ComponentOrders.EFFECTS;
   

      Game.Components.Add(smoke);
      Game.Components.Add(chainreaction);
      Game.Components.Add(plasma);
      Game.Components.Add(machine_gun);
      game.Components.Add(muzzle_flash);
      Game.Components.Add(plasma_engine);
      Game.Components.Add(warp);
      

      this.UpdateOrder = (int)ComponentOrders.EFFECTS;
      this.DrawOrder = (int)ComponentOrders.EFFECTS;
    }

    protected override void Dispose(bool disposing)
    {
      Game.Services.RemoveService(typeof(EffectsManager));
      base.Dispose(disposing);
    }

    SpriteFont vanishing_text_font;
    AnimationDescription explosion_animation;
    ShockWaveDescription shockwave_animation;
    SlideAnimationDescription thunderbolt_animation;
    SlideAnimationDescription laserline_animation;
    MissileDescription missile_description, nuke_description;
    struct AnimationDescription
    {
      public Texture2D texture;
      public float frame_length;
      public int frame_width;
      public int frame_height;
      public bool TooOld(float age)
      {
        var frames_per_row = texture.Width / frame_width;
        var frames_per_column = texture.Height / frame_height;
        return (int)(age / frame_length) > frames_per_row * frames_per_column;
      }
      public Rectangle GetFrameBounds(float age)
      {
        var frames_per_row = texture.Width / frame_width;
        int i = (int)(age / frame_length);
        var rect = new Rectangle((i % frames_per_row) * frame_width,
                             (i / frames_per_row) * frame_height,
                             frame_width, frame_height);
        return rect;
      }
    }

    struct ShockWaveDescription
    {
        public Texture2D texture;
        public bool TooOld(float range, float radius)
        {
            return radius>=range;
        }
    }
    struct MissileDescription
    {
        public Texture2D texture;
        public float scale;
    }
    struct SlideAnimationDescription
    {
        public Texture2D texture;
        public float slideSpeed;
        public float textureScale;

        public Rectangle GetFrameBounds(float age, float length)
        {
            length *= textureScale;
            //var start = (texture.Height-length)-(age * slideSpeed) % (texture.Height - length);
            var start = (age * slideSpeed) % (texture.Height - length);
            var rect = new Rectangle(0, (int)start, texture.Width, (int)length);
            return rect;
        }
    }


    ArrayList<Explosion> explosions = new ArrayList<Explosion>(100);
    Stack<Explosion> free_explosions = new Stack<Explosion>(100);

    ArrayList<ShockWave> shockwaves = new ArrayList<ShockWave>(15);
    Stack<ShockWave> free_shockwaves = new Stack<ShockWave>(15);

    ArrayList<VanishingText> vanishing_texts = new ArrayList<VanishingText>(100);
    Stack<VanishingText> free_vanishing_texts = new Stack<VanishingText>(100);
    //List<VanishingText> vanishing_texts_to_add = new ArrayList<VanishingText>(100); 

    ArrayList<LightningEffect> thunderbolts = new ArrayList<LightningEffect>(20);
    Stack<LightningEffect> free_thunderbolts = new Stack<LightningEffect>(20);
    //List<LightningEffect> thunderbolts_to_add = new ArrayList<LightningEffect>(20);

    ArrayList<LaserLineEffect> laserlines = new ArrayList<LaserLineEffect>(40);
    Stack<LaserLineEffect> free_laserlines = new Stack<LaserLineEffect>(40);
    //List<LaserLineEffect> laserlines_to_add = new ArrayList<LaserLineEffect>(40);

    ArrayList<MissileEffect> missiles = new ArrayList<MissileEffect>(40);
    Stack<MissileEffect> free_missiles = new Stack<MissileEffect>(40);

    ArrayList<MissileEffect> nukes = new ArrayList<MissileEffect>(40);
    Stack<MissileEffect> free_nukes = new Stack<MissileEffect>(40);

    public ArrayList<LaserShot> Lasers = new ArrayList<LaserShot>(10);
     Texture2D LasersTexture;

    SpriteBatch spriteBatch;

    public override void Initialize()
    {
      var camera_producer = Game.Services.GetService(typeof(CameraProvider)) as CameraProvider;
      camera_producer.AddCameraConsumer(this);
      
      base.Initialize();
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);

        state_provider = Game.Services.GetService(typeof(IStateProvider<BaseLevelStats>))
            as IStateProvider<BaseLevelStats>;

        explosion_animation = new AnimationDescription()
        {
            texture = Game.Content.Load<Texture2D>(@"CachedEffects\explosion"),
            frame_height = 100,
            frame_width = 100,
            frame_length = 1.0f / 20.0f
        };
        shockwave_animation = new ShockWaveDescription()
        {
            texture = Game.Content.Load<Texture2D>(@"Explosion\shockwave")
        };

        thunderbolt_animation = new SlideAnimationDescription()
        {
            texture = Game.Content.Load<Texture2D>(@"Projectiles\thunderbolt"),
            slideSpeed = 300f,
            textureScale = 1
        };

        laserline_animation = new SlideAnimationDescription()
        {
            texture = Game.Content.Load<Texture2D>(@"Projectiles\laser"),
            slideSpeed = 0f,
            textureScale = 1f
        };

        missile_description = new MissileDescription()
        {
            texture = Game.Content.Load<Texture2D>(@"Projectiles\missile_texture"),
            scale = 5f
        };

        nuke_description = new MissileDescription()
        {
            texture = Game.Content.Load<Texture2D>(@"Projectiles\missile_texture"),
            scale = 4f
        };

        LasersTexture = Game.Content.Load<Texture2D>(@"Explosion\blue_line");
    }


    public struct genericPartilceInfo
    {
        public float Age;
        public float AgingSpeed;
        public float MaxAge;
        public float Size;
        public Vector3 Position;
        public Vector3 Velocity;
        public bool IsActive;
        public Color Color;
        public Vector3 Gravity;
        public float Rotation;
        public Vector2 Scale;
    }
    public static genericPartilceInfo gpi;
    public static Action<Particle> init_gp = new Action<Particle>(p =>
    {
        p.Age = gpi.Age;
        p.AgingSpeed = gpi.AgingSpeed;
        p.MaxAge = gpi.MaxAge;
        p.Size = gpi.Size;
        p.Position = gpi.Position;
        p.Velocity = gpi.Velocity;
        p.IsActive = gpi.IsActive;
        p.Color = gpi.Color;
        p.Gravity = gpi.Gravity;
        p.Rotation = gpi.Rotation;
        p.Scale = gpi.Scale;
    });


    public SmokeParticle AddSmokeParticle(Vector3 position, Vector3 velocity, float size, Color color)
    {
      gpi = new genericPartilceInfo()
      {
        Age = 1.0f,
        AgingSpeed = 1.0f,
        MaxAge = 1.0f,
        Size = size / 20.0f,
        Position = position,
        Velocity = velocity,
        IsActive = true,
        Color = color,
        Gravity = Vector3.Zero,
        Rotation = 0.0f,
        Scale = new Vector2(1.0f, 1.0f)
      };
      return smoke.AddParticle(init_gp);
    }
    public PlasmaParticle AddPlasmaParticle(Vector3 position, Vector3 velocity, float size, Color color)
    {
        gpi = new genericPartilceInfo()
        {
            Age = 0.2f,
            AgingSpeed = 0.0f,
            MaxAge = 0.2f,
            Size = size / 4.0f,
            Position = position,
            Velocity = velocity,
            IsActive = true,
            Color = color,
            Gravity = Vector3.Zero,
            Rotation = 0.0f,
            Scale = new Vector2(1.0f, 1.0f)
        };
        return plasma.AddParticle(init_gp);
    }
    public PlasmaParticle AddChainReactionParticle(Vector3 position, Vector3 velocity, float size, Color color)
    {
        gpi = new genericPartilceInfo()
        {
            Age = 0.2f,
            AgingSpeed = 0.0f,
            MaxAge = 0.2f,
            Size = size / 4.0f,
            Position = position,
            Velocity = velocity,
            IsActive = true,
            Color = color,
            Gravity = Vector3.Zero,
            Rotation = 0.0f,
            Scale = new Vector2(1.0f, 1.0f)
        };
        return chainreaction.AddParticle(init_gp);
    }
    public MachineGunParticle AddMachineGunParticle(Vector3 position, Vector3 velocity, float size, Color color, float rotation)
    {
        gpi = new genericPartilceInfo()
        {
            Age = 0.2f,
            AgingSpeed = 0.0f,
            MaxAge = 0.2f,
            Size = size / 4.0f,
            Position = position,
            Velocity = velocity,
            IsActive = true,
            Color = color,
            Gravity = Vector3.Zero,
            Rotation = rotation,
            Scale = new Vector2(1.0f, 1.0f)
        };
        return machine_gun.AddParticle(init_gp);
    }

    public EngineParticle AddEngineParticle(Vector3 position, Vector3 velocity, float size, Color color)
    {
        gpi = new genericPartilceInfo()
        {
            Age = 0.2f,
            AgingSpeed = 1.0f,
            MaxAge = 0.2f,
            Size = size / 10.0f,
            Position = position,
            Velocity = velocity,
            IsActive = true,
            Color = color,
            Gravity = Vector3.Zero,
            Rotation = 0.0f,
            Scale = new Vector2(1.0f, 1.0f)
        };
        return plasma_engine.AddParticle(init_gp);
    }

    public WarpParticle AddWarparticle(Vector3 position, float size, Color color)
    {
        gpi = new genericPartilceInfo()
       {
           Age = 0.2f,
           AgingSpeed = 0.0f,
           MaxAge = 0.2f,
           Size = size / 4.0f,
           Position = position,
           Velocity = Vector3.Zero,
           IsActive = true,
           Color = color,
           Gravity = Vector3.Zero,
           Rotation = 0.0f,
           Scale = new Vector2(1.0f, 1.0f)
       };
        return warp.AddParticle(init_gp);
    }


    
      // max life sarà dato da fire_rate + number_of_shots
    public void AddLaserShot(Vector3 Shooter_Position, Vector3 Target_Position, float MaxLife, Color color)
    {
        Lasers.Add(
            new LaserShot()
            {
                ShooterPosition = Shooter_Position,
                TargetPosition = Target_Position,
                MAXLIFE = MaxLife,
                life = 0.0f,
                LaserColor = color
                
            });
    }

    public LightningEffect AddThunderbolt(Action<LightningEffect> init)
    {
        if (free_thunderbolts.Count <= 0) free_thunderbolts.Push(new LightningEffect());
        var lig = free_thunderbolts.Pop();
        init(lig);
        thunderbolts.Add(lig);
        return lig;
    }

    public LaserLineEffect AddLaserLine(Action<LaserLineEffect> init)
    {
        if (free_laserlines.Count <= 0) free_laserlines.Push(new LaserLineEffect());
        var lig = free_laserlines.Pop();
        init(lig);
        laserlines.Add(lig);
        return lig;
    }

    public MissileEffect AddMissile(Action<MissileEffect> init)
    {
        if (free_missiles.Count <= 0) free_missiles.Push(new MissileEffect());
        var m = free_missiles.Pop();
        init(m);
        missiles.Add(m);
        return m;
    }
    public MissileEffect AddNuke(Action<MissileEffect> init)
    {
        if (free_nukes.Count <= 0) free_nukes.Push(new MissileEffect());
        var n = free_nukes.Pop();
        init(n);
        nukes.Add(n);
        return n;
    }
    public MuzzleFlashParticle AddMuzzleFlashParticle(Vector3 position, float size, Color color, float rotation)
    {
        gpi = new genericPartilceInfo
        {
            Age = 0.5f,
            AgingSpeed = 1.0f,
            MaxAge = 0.51f,
            Size = size / 4.0f,
            Position = position,
            Velocity = Vector3.Zero,
            IsActive = true,
            Color = color,
            Gravity = Vector3.Zero,
            Rotation = rotation,
            Scale = new Vector2(1.0f, 1.0f)
        };
        return muzzle_flash.AddParticle(init_gp);
    }

    public void AddExplosion(Action<Explosion> init)
    {
        if (free_explosions.Count <= 0) free_explosions.Push(new Explosion());
        var exp = free_explosions.Pop();

        init(exp);

        explosions.Add(exp);
    }

    public void AddVanishingText(Action<VanishingText> init)
    {
        if (free_vanishing_texts.Count <= 0) free_vanishing_texts.Push(new VanishingText());
        var exp = free_vanishing_texts.Pop();

        init(exp);

        vanishing_texts.Add(exp);
    }
    public void AddShockWave(Action<ShockWave> init)
    {
        if (free_shockwaves.Count <= 0) free_shockwaves.Push(new ShockWave());
        var exp = free_shockwaves.Pop();

        init(exp);

        shockwaves.Add(exp);
        //shockwaves_to_add.Add(shockwave);
    }

    public class ShockWave
    {
        public Vector3 position;
        public Color initialColor;
        public Color finalColor;
        public float range;
        public float radius;
        public float speed;
        public float status { get { return Math.Min(1.0f, radius / range); } }
        public void Update(GameTime gameTime, float slow_motion,bool enabled)
        {
            if(enabled)
                radius += speed * (float)gameTime.ElapsedGameTime.TotalSeconds * slow_motion;// *speed;
        }
    }

    public override void Update(GameTime gameTime)
    {
        var dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

        //explosions.AddRange(explosions_to_add);
        //explosions_to_add.Clear();        
        //List<Explosion> explosions_to_remove = new ArrayList<Explosion>();
        //for_each (var e in explosions)
        //{
        //    e.Update(gameTime);
        //    if (explosion_animation.TooOld(e.age))
        //        explosions_to_remove.Add(e);
        //}
        //for_each (var e in explosions_to_remove) explosions.Remove(e);

        for (int i = 0; i < explosions.Count; i++)
        {
            var exp= explosions[i];
            if (explosion_animation.TooOld(exp.age))
            {
                explosions.Remove(exp);
                free_explosions.Push(exp);
                i--;
            }
            else
            {
                exp.Update(gameTime);
            }
        }
        //for_each (var e in explosions_to_remove) explosions.Remove(e);


        for (int i = 0; i < shockwaves.Count; i++)
        {
            var s = shockwaves[i];
            if (shockwave_animation.TooOld(s.range, s.radius))
            {
                shockwaves.Remove(s);
                free_shockwaves.Push(s);
                i--;
            }
            else
            {
                s.Update(gameTime, state_provider.CurrentState.slow_motion,state_provider.Enabled);
            }
        }
        //shockwaves.AddRange(shockwaves_to_add);
        //shockwaves_to_add.Clear();
        //List<ShockWave> shockwaves_to_remove = new ArrayList<ShockWave>();
        //for_each (var s in shockwaves)
        //{
        //    s.Update(gameTime);
        //    if (shockwave_animation.TooOld(s.range, s.radius))
        //        shockwaves_to_remove.Add(s);
        //}
        //for_each (var s in shockwaves_to_remove) shockwaves.Remove(s);

        //thunderbolts.AddRange(thunderbolts_to_add);
        //thunderbolts_to_add.Clear();
        //List<LightningEffect> thunderbolts_to_remove = new ArrayList<LightningEffect>();
        //for_each (var e in thunderbolts)
        //{
        //    e.Update(gameTime);
        //    if (e.duration <= 0.0f)
        //        thunderbolts_to_remove.Add(e);
        //}
        //for_each (var e in thunderbolts_to_remove) thunderbolts.Remove(e);
        for(int i=0; i<thunderbolts.Count; i++)
        {
            var e = thunderbolts[i];            
            if (e.duration <= 0.0f)
            {
                thunderbolts.Remove(e);
                free_thunderbolts.Push(e);
                i--;
            }
            else
                e.Update(gameTime);
        }

        //laserlines.AddRange(laserlines_to_add);
        //laserlines_to_add.Clear();
        //List<LaserLineEffect> laserlines_to_remove = new ArrayList<LaserLineEffect>();
        //for_each (var e in laserlines)
        //{
        //    e.Update(gameTime);
        //    if (e.duration <= 0.0f)
        //        laserlines_to_remove.Add(e);
        //}
        //for_each (var e in laserlines_to_remove) laserlines.Remove(e);
        for (int i = 0; i < laserlines.Count; i++)
        {
            var e = laserlines[i];
            if (e.duration <= 0.0f)
            {
                laserlines.Remove(e);
                free_laserlines.Push(e);
                i--;
            }
            else
                e.Update(gameTime);
        }

        for (int i = 0; i < missiles.Count; i++)
        {
            var e = missiles[i];
            //if (e.duration <= 0.0f)
            if(!e.projectileActive)
            {
                missiles.Remove(e);
                free_missiles.Push(e);
                i--;
            }
            else
                e.Update(gameTime);
        }
        for (int i = 0; i < nukes.Count; i++)
        {
            var e = nukes[i];
            //if (e.duration <= 0.0f)
            if (!e.projectileActive)
            {
                nukes.Remove(e);
                free_nukes.Push(e);
                i--;
            }
            else
                e.Update(gameTime);
        }
        //vanishing_texts.AddRange(vanishing_texts_to_add);
        //vanishing_texts_to_add.Clear();
        //List<VanishingText> vanishing_texts_to_remove = new ArrayList<VanishingText>();
        //for_each (var e in vanishing_texts)
        //{
        //    e.Update(gameTime);
        //    if (e.time_left <= 0.0f)
        //        vanishing_texts_to_remove.Add(e);
        //}
        //for_each (var e in vanishing_texts_to_remove) vanishing_texts.Remove(e);

        for (int i = 0; i < vanishing_texts.Count; i++)
        {
            var e = vanishing_texts[i];
            if (e.time_left <= 0.0f)
            {
                vanishing_texts.Remove(e);
                free_vanishing_texts.Push(e);
                i--;
            }
            else
                e.Update(gameTime);
        }

        chainreaction.SetCamera(view, projection, max_depth);
        plasma.SetCamera(view, projection, max_depth);
        smoke.SetCamera(view, projection, max_depth);
        plasma_engine.SetCamera(view, projection, max_depth);
        warp.SetCamera(view, projection, max_depth);
        for (int i = 0; i < Lasers.Count; i++)
        {

            Lasers[i].life += dt;
            if (Lasers[i].life > Lasers[i].MAXLIFE)
            {
                Lasers.RemoveAt(i);
                i--;
            }
        }
        base.Update(gameTime);
    }

    public override void Draw(GameTime gameTime)
    {
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
        for (int i = 0; i < shockwaves.Count; i++)
        {
            var s = shockwaves[i];
            var sp = GraphicsDevice.Viewport.Project(s.position, projection, view, Matrix.Identity);
            var color = Color.Lerp(s.initialColor, s.finalColor, s.status);
            var alpha = 1;
            var r = s.radius * 30;
            spriteBatch.Draw(
                shockwave_animation.texture,
                new Rectangle((int)(sp.X - r), (int)(sp.Y - r), (int)(r * 2), (int)(r * 2)),
                color * alpha
            );
        }
        spriteBatch.End();

        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
        //for_each (var e in explosions)
        for (int i = 0; i < explosions.Count; i++)
        {
            var e = explosions[i];
            var sp = GraphicsDevice.Viewport.Project(e.position, projection, view, Matrix.Identity);
            var rect = explosion_animation.GetFrameBounds(e.age);
            spriteBatch.Draw(explosion_animation.texture, new Vector2(sp.X, sp.Y), rect, Color.White, 0.0f,
                              new Vector2(rect.Width, rect.Height) * 0.5f, e.size * max_depth / (Vector3.Distance(e.position, EyePosition)),
                              SpriteEffects.None, 0.0f);
        }
        spriteBatch.End();

        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
        //for_each (var e in thunderbolts)
        for (int i = 0; i < thunderbolts.Count; i++)
        { 
            var e = thunderbolts[i];
            
            var zoomScale = 1 / Vector3.Distance(EyePosition, e.startPoint);
                       
            var sp = GraphicsDevice.Viewport.Project(e.startPoint, projection, view, Matrix.Identity);
            var dp = GraphicsDevice.Viewport.Project(e.endPoint, projection, view, Matrix.Identity);

            //var dir = (e.destination-e.source);
            //ir.Normalize();
            //var ang = (float)GetSignedAngleBetween2DVectors(Vector3.Backward,dir,Vector3.Right);
            //var ang = (float)((Math.Atan2((float)(dp.Z - sp.Z), (float)(dp.X - sp.X)) + 3 * Math.PI / 2) % (Math.PI * 2));

            //(float)Math.Acos(Vector3.Dot(Vector3.Backward, dir));
            var rect = thunderbolt_animation.GetFrameBounds(e.duration, (float)Math.Abs(Vector3.Distance(sp, dp) * (1 / (e.size)) * (1/zoomScale)));
            spriteBatch.Draw(thunderbolt_animation.texture, new Vector2(sp.X, sp.Y), rect, Color.White,
                            e.rotation, new Vector2(rect.Width, 0) * 0.5f, 
                            thunderbolt_animation.textureScale * e.size * zoomScale, SpriteEffects.None, 0.0f);
        }
        spriteBatch.End();


        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
        //for_each (var e in laserlines)
        for (int i = 0; i < laserlines.Count;i++ )
        {
            var e = laserlines[i];
            var zoomScale = 1 / Vector3.Distance(EyePosition, e.startPoint);
            
            if (e.projectileActive)
                continue;
            var sp = GraphicsDevice.Viewport.Project(e.startPoint, projection, view, Matrix.Identity);
            var dp = GraphicsDevice.Viewport.Project(e.endPoint, projection, view, Matrix.Identity);

            //var dir = (e.destination-e.source);
            //ir.Normalize();
            //var ang = (float)GetSignedAngleBetween2DVectors(Vector3.Backward,dir,Vector3.Right);
            //var ang = (float)((Math.Atan2((float)(dp.Z - sp.Z), (float)(dp.X - sp.X)) + 3 * Math.PI / 2) % (Math.PI * 2));

            //(float)Math.Acos(Vector3.Dot(Vector3.Backward, dir));
            var rect = laserline_animation.GetFrameBounds(e.duration, (float)Math.Abs(Vector3.Distance(sp, dp)*(1/e.size)*(1/zoomScale) ));
            spriteBatch.Draw(laserline_animation.texture, new Vector2(sp.X, sp.Y), rect, Color.White, e.rotation, 
                                new Vector2(rect.Width, 0) * 0.5f, laserline_animation.textureScale*e.size*zoomScale,
                                SpriteEffects.None, 0.0f);
            
        }
        spriteBatch.End();

        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
        //for_each (var e in laserlines)
        for (int i = 0; i < missiles.Count; i++)
        {
            var e = missiles[i];
            if (e.projectile.geometry.CollisionSkin == null || e.projectile.geometry.CollisionSkin.Collisions.Count==0)
            {
                //if (e.projectile.OwnValue.IsActive)
                //    continue;

                var zoomScale = 20 / Vector3.Distance(EyePosition, e.projectile.geometry.Position);

                var sp = GraphicsDevice.Viewport.Project(e.projectile.geometry.Position, projection, view, Matrix.Identity);
                var dp = GraphicsDevice.Viewport.Project(e.projectile.geometry.Position + e.projectile.geometry.Velocity, projection, view, Matrix.Identity);

                //var rect = laserline_animation.GetFrameBounds(e.duration, (float)Math.Abs(Vector3.Distance(sp, dp)));
                var rect = missile_description.texture.Bounds;
                spriteBatch.Draw(missile_description.texture, new Vector2(sp.X, sp.Y), rect, Color.White,
                                    e.rotation, new Vector2(rect.Width, 0) * 0.5f, (1f / missile_description.scale) * zoomScale, SpriteEffects.None, 0.0f);
            }
        }
        spriteBatch.End();

        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
        //for_each (var e in laserlines)
        for (int i = 0; i < nukes.Count; i++)
        {
            var e = nukes[i];
            var zoomScale = 20 / Vector3.Distance(EyePosition, e.projectile.geometry.Position);
            //if (e.projectile.OwnValue.IsActive)
            //    continue;
            if (e.projectile.geometry.CollisionSkin == null || e.projectile.geometry.CollisionSkin.Collisions.Count == 0)
            {
                var sp = GraphicsDevice.Viewport.Project(e.projectile.geometry.Position, projection, view, Matrix.Identity);
                var dp = GraphicsDevice.Viewport.Project(e.projectile.geometry.Position + e.projectile.geometry.Velocity, projection, view, Matrix.Identity);

                //var rect = laserline_animation.GetFrameBounds(e.duration, (float)Math.Abs(Vector3.Distance(sp, dp)));
                var rect = nuke_description.texture.Bounds;
                spriteBatch.Draw(nuke_description.texture, new Vector2(sp.X, sp.Y), rect, Color.White,
                                    e.rotation, new Vector2(rect.Width, 0) * 0.5f, (1f / nuke_description.scale)*zoomScale, SpriteEffects.None, 0.0f);
            }
        }
        spriteBatch.End();

        spriteBatch.Begin();
        //for_each (var v in vanishing_texts)
        for (int i = 0; i < vanishing_texts.Count; i++)
        {
            var v = vanishing_texts[i];
            var sp = GraphicsDevice.Viewport.Project(v.position, projection, view, Matrix.Identity);
            var alpha = v.time_left;
            var size = vanishing_text_font.MeasureString(v.text);
            spriteBatch.DrawString(vanishing_text_font, v.text, new Vector2(sp.X, sp.Y + 8 * v.time_left), Color.DarkBlue,
              0.0f, size * 0.5f, MathHelper.SmoothStep(0, 1, v.time_left), SpriteEffects.None, 0.0f);
            spriteBatch.DrawString(vanishing_text_font, v.text, new Vector2(sp.X + 1, sp.Y + 1 + 8 * v.time_left), Color.CornflowerBlue,
              0.0f, size * 0.5f, MathHelper.SmoothStep(0, 1, v.time_left), SpriteEffects.None, 0.0f);
        }
        spriteBatch.End();
        spriteBatch.Begin();
        //for_each (var l in Lasers)
        for (int i = 0; i < Lasers.Count; i++)
        {
            var l = Lasers[i];
            var sp = GraphicsDevice.Viewport.Project(l.Centroid, projection, view, Matrix.Identity);

            spriteBatch.Draw(LasersTexture, new Vector2(sp.X, sp.Y), null, Color.White, l.Angle + MathHelper.PiOver2,
                              new Vector2(LasersTexture.Width / 2, LasersTexture.Height / 2), l.Scale,
                              SpriteEffects.None, 0.0f);
        }
        spriteBatch.End();
        base.Draw(gameTime);
    }

    public void Reset()
    {
      explosions.Clear();
      shockwaves.Clear();
      laserlines.Clear();
      thunderbolts.Clear();
      vanishing_texts.Clear();
      Lasers.Clear();
      plasma.Reset();
      chainreaction.Reset();
      plasma_engine.Reset();
      smoke.Reset();
      warp.Reset();
      machine_gun.Reset();
      muzzle_flash.Reset();
      missiles.Clear();
      nukes.Clear(); 
    }
  }
}
